/**
 * Copyright 2016-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.inmorn.extspring.annotationbean;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;

import com.inmorn.extspring.annotationbean.annotation.AnnotationProcessr;
import com.inmorn.extspring.annotationbean.annotation.Bean;
import com.inmorn.extspring.annotationbean.annotation.Beans;
import com.inmorn.extspring.annotationbean.annotation.Element;
import com.inmorn.extspring.annotationbean.annotation.Entry;
import com.inmorn.extspring.annotationbean.annotation.MyBatisMapper;
import com.inmorn.extspring.annotationbean.annotation.Property;
import com.inmorn.extspring.annotationbean.exception.AnnotationBeanException;
import com.inmorn.extspring.annotationbean.exception.AnnotationNullException;
import com.inmorn.extspring.annotationbean.support.AbstractAnnotationProcessr;
import com.inmorn.extspring.hessian.ExtHessianServiceExporter;
import com.inmorn.extspring.hessian.annotation.Hessian;
import com.inmorn.extspring.jdbc.annotation.JdbcDao;
import com.inmorn.extspring.util.ClassUtils;
import com.inmorn.extspring.util.StringUtils;

/**
 * 扫描自定义注解类添加到Spring boot上下文中
 * @author Jeff.Li
 * 2017-07-13
 */
@SuppressWarnings("rawtypes")
public class CustomizeApplicationContextInitializer implements ApplicationContextInitializer{
	
	/**
	 * 扫描class路径
	 */
	private String basePackage = "com";
	private final Logger LOG = LoggerFactory.getLogger(getClass());
	private ConfigurableApplicationContext applicationContext;
	private Environment environment;
	private Map<String, AbstractAnnotationProcessr> annotationProcessrMap = 
			new HashMap<String, AbstractAnnotationProcessr>();
	
	@SuppressWarnings("unchecked")
	public void registerCustomizeAnnotationBean(){
		DefaultListableBeanFactory acf = (DefaultListableBeanFactory) 
				applicationContext.getBeanFactory();
        
        /**
         * 获取指定package下所有class
         */
        Set<Class<?>> classList = ClassUtils.getClassSet(basePackage);
        System.out.println("===========================:" + classList.size());
        
        /**
         * 初始化自定义注解转换类,提供自定义注解Bean使用
         */
        for(Class<?> clazz : classList){
        	if(clazz.getAnnotation(AnnotationProcessr.class) != null){
        		Object object = null;
				try {
					object = clazz.newInstance();
				} catch (Exception e) {
					e.printStackTrace();
					throw new AnnotationBeanException("Class:" + clazz + ",newInstance exception");
				}
        		AbstractAnnotationProcessr annotationProcessr = (AbstractAnnotationProcessr) object;
        		Class<? extends Annotation> annotation = annotationProcessr.getAnnotation();
        		if(annotation == null){
        			LOG.error("Class:{},method getAnnotation() is null",clazz);
        			throw new AnnotationNullException("Class:" + clazz + " method getAnnotation() is null");
        		}
        		
        		annotationProcessr.init(applicationContext, null, environment);
        		annotationProcessrMap.put(annotation.getName(), annotationProcessr);
        	}
        }
        
        for (Class<?> clazz : classList) {
        	/**
        	  * 1.将com.inmorn.context.beans.annotation.Bean注解转换成BeanDefinition添加到Spring上下文中
        	 */
        	if(clazz.getAnnotation(Beans.class) != null){
        		Field[] fields = clazz.getDeclaredFields();
        		for (Field field : fields) {
        			/**
        			 * 判断有Bean注解的属性
        			 */
					Bean bean = field.getAnnotation(Bean.class);
					if(bean != null){
						/**
						 * 设置bean基本信息
						 */
						String beanClass = StringUtils.isEmpty(bean.classes()) ? field.getType().getName().toString() : bean.classes();
						BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(beanClass);
						String beanId = StringUtils.isEmpty(bean.id()) ? field.getName() : bean.id();
						bdb.getBeanDefinition().setAttribute("id", beanId);
						
						bdb.setAbstract(bean.beanAbstract());
						bdb.setLazyInit(bean.lazyInit());
						bdb.setScope(bean.scope());
						
						if(StringUtils.isNotEmpty(bean.parent())) bdb.setParentName(bean.parent());
						if(StringUtils.isNotEmpty(bean.destroyMethodName())) bdb.setDestroyMethodName(bean.destroyMethodName());
						if(StringUtils.isNotEmpty(bean.factoryMethod())) bdb.setFactoryMethod(bean.factoryMethod());
						if(StringUtils.isNotEmpty(bean.initMethodName())) bdb.setInitMethodName(bean.initMethodName());
						
						Property[] propertys = bean.propertys();
						if(propertys != null && propertys.length > 0){
							for(Property property : propertys){
								String propertyName = property.name();
								/**
								 * 如果value不为空则赋值
								 */
								String value = property.value();
								if(StringUtils.isNotEmpty(value)){
									String propKey = StringUtils.parsePropertiesKey(value);
									if(StringUtils.isNotEmpty(propKey))
										value = environment.getProperty(propKey);
									bdb.addPropertyValue(propertyName, value);
									continue;
								}
								
								/**
								 * 如果ref不为空则引用
								 */
								String ref = property.ref();
								if(StringUtils.isNotEmpty(ref)){
									bdb.addPropertyValue(propertyName, new RuntimeBeanReference(ref));
									continue;
								}

								/**
								 * 如果list不为空则赋值
								 */
								Element[] elements = property.list();
								if(elements != null && elements.length > 0){
									List<String> list = new ArrayList<String>();
									for(Element element : elements) list.add(element.value());
									bdb.addPropertyValue(propertyName, list);
									continue;
								}
								
								/**
								 * 如果map不为空则赋值
								 */
								Entry[] entrys = property.map();
								if(entrys != null && entrys.length > 0){
									Map map = new HashMap();
									for(Entry entry : entrys) map.put(entry.key(),entry.value());
									bdb.addPropertyValue(propertyName, map);
									continue;
								}
							}
						}
						/**
						 * 注册bean
						 */
				        acf.registerBeanDefinition(beanId, bdb.getBeanDefinition());
				        LOG.info("register BeanDefinition : @Bean[" + beanId + "]");
					}
				}
        	}
        	/**
        	 * 2.将com.inmorn.extspring.jdbc.annotation.JdbcDao注解转换成BeanDefinition添加到Spring上下文中
        	 */
        	else if(clazz.getAnnotation(JdbcDao.class) != null){
        		JdbcDao jdbcDao = clazz.getAnnotation(JdbcDao.class);
        		BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(clazz);
        		String beanId = StringUtils.firstToLowerCase(clazz.getSimpleName());
        		bdb.getBeanDefinition().setAttribute("id", beanId);
        		
        		/**
        		 * 找出dataSouce
        		 */
        		String dataSource = jdbcDao.dataSource();
        		String propKey = StringUtils.parsePropertiesKey(dataSource);
				if(StringUtils.isNotEmpty(propKey)){
					String propertyValue = environment.getProperty(propKey);
					dataSource = StringUtils.isNotEmpty(propertyValue) ? propertyValue : propKey;
				}
				
				bdb.addPropertyValue("dataSource", new RuntimeBeanReference(dataSource));
				acf.registerBeanDefinition(beanId, bdb.getBeanDefinition());
				LOG.info("register BeanDefinition : @JdbcDao[" + beanId + "]");
        	}
        	/**
        	 * 3.将com.inmorn.extspring.hessian.annotation.Hessian注解转换成BeanDefinition添加到Spring上下文中
        	 */
        	else if(clazz.getAnnotation(Hessian.class) != null){
        		Hessian hessian = clazz.getAnnotation(Hessian.class);
        		String uri = hessian.uri();
                boolean server = hessian.server();
                boolean overloadEnabled = hessian.overloadEnabled();
                
                BeanDefinitionBuilder bdb = BeanDefinitionBuilder.rootBeanDefinition(
                		server ? ExtHessianServiceExporter.class : HessianProxyFactoryBean.class);
        		String beanId = server ? uri : StringUtils.firstToLowerCase(clazz.getSimpleName());
        		bdb.getBeanDefinition().setAttribute("id", beanId);
        		
                /**
                 * 为客户端创建hessian接口
                 */
                if(!server){
                	if(StringUtils.isNotEmpty(uri)){
                		String propKey = StringUtils.parsePropertiesKey(uri);
                		if(StringUtils.isNotEmpty(propKey)){
                			String propertyValue = environment.getProperty(propKey);
                			uri = StringUtils.isNotEmpty(propertyValue) ? propertyValue : propKey;
                		}
                	}
                	bdb.addPropertyValue("serviceUrl", uri);
                	bdb.addPropertyValue("serviceInterface", clazz.getName());
                    //新增overloadEnabled属性，并把它的值设置为true，默认是false，则Hessian就能支持方法的重载了
                	bdb.addPropertyValue("overloadEnabled", overloadEnabled);
                }
                /**
                 * 创建hessian服务端
                 */
                else{
                	//the mapper interface is the original class of the bean
                    //but, the actual class of the bean is HessianServiceExporter
                	Class<?> serviceImpl = (Class<?>) hessian.serverImpl();
                    bdb.addPropertyValue("serviceInterface", clazz.getName());
                    bdb.addPropertyValue("service", new RuntimeBeanReference(StringUtils.firstToLowerCase(serviceImpl.getSimpleName())));
                }
                acf.registerBeanDefinition(beanId, bdb.getBeanDefinition());
                LOG.info("register BeanDefinition : @Hessian[" + beanId + "]");
        	}
        	/**
        	 * 4.将com.inmorn.extspring.annotationbean.annotation.MyBatisMapper注解转换成BeanDefinition添加到Spring上下文中
        	 */
        	else if(clazz.getAnnotation(MyBatisMapper.class) != null){
        		MyBatisMapper mapper = clazz.getAnnotation(MyBatisMapper.class);
        		BeanDefinitionBuilder bdb = null;
				try {
					bdb = BeanDefinitionBuilder.rootBeanDefinition(
							Class.forName("org.mybatis.spring.mapper.MapperFactoryBean"));
					String beanId = StringUtils.firstToLowerCase(clazz.getSimpleName());
					bdb.getBeanDefinition().setAttribute("id", beanId);
					bdb.addPropertyValue("mapperInterface",clazz);
					bdb.addPropertyValue("addToConfig", mapper.addToConfig());
					
					String sqlSessionFactoryBeanName = mapper.sqlSessionFactory();
					if(StringUtils.isEmpty(sqlSessionFactoryBeanName)){
						sqlSessionFactoryBeanName = "sqlSessionFactory";
					}
					
					String sqlSessionTemplateBeanName = mapper.sqlSessionTemplate();
					bdb.addPropertyValue("sqlSessionFactory", new RuntimeBeanReference(sqlSessionFactoryBeanName));
					if(StringUtils.isNotEmpty(sqlSessionTemplateBeanName)){
						bdb.addPropertyValue("sqlSessionTemplate", new RuntimeBeanReference(sqlSessionTemplateBeanName));
					}
					
					acf.registerBeanDefinition(beanId, bdb.getBeanDefinition());
	                LOG.info("register BeanDefinition : @MyBatisMapper[" + beanId + "]");
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
        	}
        	/**
        	 * 执行自定义注解注册类
        	 */
        	else{
        		Annotation[] annotations = clazz.getAnnotations();
        		if(annotations != null && annotations.length > 0 && annotationProcessrMap.size() > 0){
        			for(Annotation annotation : annotations){
        				AbstractAnnotationProcessr abstractAnnotationProcessr = 
        						annotationProcessrMap.get(annotation.annotationType().getName());
        				if(abstractAnnotationProcessr != null){
        					BeanDefinition beanDefinition = abstractAnnotationProcessr.classParseBeanDefinition(clazz,annotation);
        					if(beanDefinition != null){
        						String beanId = (String) beanDefinition.getAttribute("id");
        						acf.registerBeanDefinition(beanId, beanDefinition);
        						LOG.info("register BeanDefinition : {}[{}]",annotation,beanId);
        					}
        				}
        			}
        		}
        	}
        }
	}

	public String getBasePackage() {
		return basePackage;
	}

	public void setBasePackage(String basePackage) {
		this.basePackage = basePackage;
	}

	public void initialize(ConfigurableApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		try{
			environment = applicationContext.getEnvironment();
		}catch(Exception e){
			e.printStackTrace();
		}
		
		registerCustomizeAnnotationBean();
	}

}
